//
//  SoundController.m
//
//	Serial Remote for Mac - Control mac actions via serial commands
//	Copyright (C) 2010  Jeremiah McConnell
//
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#import "SoundController.h"
#import <AudioToolbox/AudioServices.h>

static SoundController *SoundControllerSingleton = nil;

// The currently active audio device.
AudioDeviceID outputDeviceID = kAudioObjectUnknown;

@implementation SoundController

#pragma mark -
#pragma mark Class Methods

+ (SoundController *)sharedSoundController
{
	@synchronized(self) {
		if (SoundControllerSingleton == nil) {
			SoundControllerSingleton = [[self alloc] init];
		}
	}
	return SoundControllerSingleton;
}

#pragma mark -
#pragma mark Sound Medthods

- (void)setDefaultAudioDevice
{
	UInt32 propertySize = 0;
	OSStatus status = noErr;
	AudioObjectPropertyAddress propertyAOPA;
	
	propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
	propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal;
	propertyAOPA.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
	propertySize = sizeof(AudioDeviceID);
	
	status = AudioHardwareServiceGetPropertyData(kAudioObjectSystemObject, &propertyAOPA, 0, NULL, &propertySize, &outputDeviceID);	
	
	if(status) {
		// Error
		return;
	}
}

// Retrieve the volume value from the hardware
- (float)getHardwareVolume 
{
	Float32			outputVolume;
	
	UInt32 propertySize = 0;
	OSStatus status = noErr;
	AudioObjectPropertyAddress propertyAOPA;
	
	//if(outputDeviceID == kAudioObjectUnknown)
	[self setDefaultAudioDevice];
	
	propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
	propertyAOPA.mScope = kAudioDevicePropertyScopeOutput;
	propertyAOPA.mSelector = kAudioHardwareServiceDeviceProperty_VirtualMasterVolume;
	propertySize = sizeof(Float32);
	
	status = AudioHardwareServiceGetPropertyData(outputDeviceID, &propertyAOPA, 0, NULL, &propertySize, &outputVolume);
	
	if(status) {
		// Error
		return 0.0;
	}
	
	if (outputVolume < 0.0 || outputVolume > 1.0)
		return 0.0;
	else
		return outputVolume;
}


// mute system volume
- (void)muteHardwareVolume
{
	UInt32 propertySize = 0;
	OSStatus status = noErr;
	AudioObjectPropertyAddress propertyAOPA;
	
	[self setDefaultAudioDevice];
	
	propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
	propertyAOPA.mScope = kAudioDevicePropertyScopeOutput;
	
	propertyAOPA.mSelector = kAudioDevicePropertyMute;
	
	propertySize = sizeof(UInt32);
	UInt32 mute = 1;
	status = AudioHardwareServiceSetPropertyData(outputDeviceID, &propertyAOPA, 0, NULL, propertySize, &mute);
	
	if(status) {
		// Error
		return;
	}
}

// umute system volume
- (void)unmuteHardwareVolume
{
	UInt32 propertySize = 0;
	OSStatus status = noErr;
	AudioObjectPropertyAddress propertyAOPA;
	
	[self setDefaultAudioDevice];
	
	propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
	propertyAOPA.mScope = kAudioDevicePropertyScopeOutput;
	
	propertyAOPA.mSelector = kAudioDevicePropertyMute;
	
	propertySize = sizeof(UInt32);
	UInt32 mute = 0;
	status = AudioHardwareServiceSetPropertyData(outputDeviceID, &propertyAOPA, 0, NULL, propertySize, &mute);
	
	if(status) {
		// Error
		return;
	}
}


// setting system volume - mutes if under threshhold 
- (Float32)setHardwareVolume:(Float32)newVolume
{
	Float32 volumeToSet;
	UInt32 propertySize = 0;
	OSStatus status = noErr;
	AudioObjectPropertyAddress propertyAOPA;
	
	[self setDefaultAudioDevice];
	
	propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
	propertyAOPA.mScope = kAudioDevicePropertyScopeOutput;
	
	if (newVolume < 0.001)
		propertyAOPA.mSelector = kAudioDevicePropertyMute;
		else
			propertyAOPA.mSelector = kAudioHardwareServiceDeviceProperty_VirtualMasterVolume;
			
			if (propertyAOPA.mSelector == kAudioDevicePropertyMute)
			{
				propertySize = sizeof(UInt32);
				UInt32 mute = 1;
				volumeToSet = 0.0;
				status = AudioHardwareServiceSetPropertyData(outputDeviceID, &propertyAOPA, 0, NULL, propertySize, &mute);
				
				if(status) {
					[[NSAlert alertWithMessageText:@"Unable to mute device." defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:nil] runModal];
					return 0.0;
				}
			}
			else
			{
				propertySize = sizeof(Float32);
				
				if (newVolume > 1.0)
					volumeToSet = 1.0;
				else
					volumeToSet = newVolume;
				
				status = AudioHardwareServiceSetPropertyData(outputDeviceID, &propertyAOPA, 0, NULL, propertySize, &volumeToSet);	
				
				if(status) {
					// Error
					return 0.0;
				}
				
				// make sure we're not muted
				propertyAOPA.mSelector = kAudioDevicePropertyMute;
				propertySize = sizeof(UInt32);
				UInt32 mute = 0;
				
				status = AudioHardwareServiceSetPropertyData(outputDeviceID, &propertyAOPA, 0, NULL, propertySize, &mute);
				
				if(status) {
					// Error
					return 0.0;
				}
			}
	
	return volumeToSet;
}


#pragma mark -
#pragma mark Control Methods

// increase volume by quack value
- (NSUInteger)increaseVolume
{
	Float32 currentVolume;
	currentVolume = [self getHardwareVolume];
	return [[NSNumber numberWithFloat:[self setHardwareVolume: currentVolume + 0.0625] * 100] unsignedIntegerValue];
}


// decrease volume by quack value
- (NSUInteger)decreaseVolume
{
	Float32 currentVolume;
	currentVolume = [self getHardwareVolume];
	return [[NSNumber numberWithFloat:[self setHardwareVolume: currentVolume - 0.0625] * 100] unsignedIntegerValue];
}


// Mute volume
- (BOOL)toggleMute
{
	static BOOL muted = 0;
	
	if (muted) {
		[self unmuteHardwareVolume];
		muted = 0;
	} else {
		[self muteHardwareVolume];
		muted = 1;
	}
	
	return muted;
}

@end
